package cn.cjj.gulimall.product.service.impl;

import cn.cjj.gulimall.product.service.CategoryBrandRelationService;
import cn.cjj.gulimall.product.vo.Catalog2Vo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.cjj.common.utils.PageUtils;
import cn.cjj.common.utils.Query;

import cn.cjj.gulimall.product.dao.CategoryDao;
import cn.cjj.gulimall.product.entity.CategoryEntity;
import cn.cjj.gulimall.product.service.CategoryService;


@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        List<CategoryEntity> entities = baseMapper.selectList(null);

        return entities;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> path = new ArrayList<>();
        path = findParentPath(catelogId, path);
        Collections.reverse(path);

        return path.toArray(new Long[0]);
    }


    @Caching(evict = {
            @CacheEvict(value = "category", key = "'getLevel1Categorys'"),
            @CacheEvict(value = "category", key = "'getCatelog2Json'")
    })
    @Override
    public void updateCascade(CategoryEntity category) {
        System.out.println(category);
        this.updateById(category);

        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    @Cacheable(value = "category", key = "#root.method.name", sync = true)
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_cid", 0);

        List<CategoryEntity> categorys = this.list(wrapper);

        return categorys;
    }


    @Cacheable(value = "category", key = "#root.methodName", sync = true)
    @Override
    public Map<String, List<Catalog2Vo>> getCatelog2Json() {
        return getCatelog2JsonFromDB();
    }

    public Map<String, List<Catalog2Vo>> getCatelog2Json2() {
        String catelogJson = null;
        do {
            catelogJson = redisTemplate.opsForValue().get("catelogJson");
            if (StringUtils.isEmpty(catelogJson)) {
//                Map<String, List<Catalog2Vo>> catelog2JsonFromDB = getCatelog2JsonFromDBWithRedisLock();
                Map<String, List<Catalog2Vo>> catelog2JsonFromDB = getCatelog2JsonFromDBWithRedisson();
                if (catelog2JsonFromDB != null) {
                    return catelog2JsonFromDB;
                }
            }
        } while (StringUtils.isEmpty(catelogJson));

        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });

        return result;
    }

    private Map<String, List<Catalog2Vo>> getCatelog2JsonFromDBWithRedisson() {
        Map<String, List<Catalog2Vo>> catelog2JsonFromDB;
        RLock lock = redissonClient.getLock("catelog-lock");
        boolean b = lock.tryLock();
        if (b) {
            try {
                catelog2JsonFromDB = getCatelog2JsonFromDB();
                redisTemplate.opsForValue().set("catelogJson", JSON.toJSONString(catelog2JsonFromDB));
            } finally {
                lock.unlock();
            }

            return catelog2JsonFromDB;
        } else {
            return null;
        }
    }

    public Map<String, List<Catalog2Vo>> getCatelog2JsonFromDBWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        Map<String, List<Catalog2Vo>> catelog2JsonFromDB = null;
        if (lock) {
            try {
                catelog2JsonFromDB = getCatelog2JsonFromDB();
                redisTemplate.opsForValue().set("catelogJson", JSON.toJSONString(catelog2JsonFromDB));
            } finally {
//            String lockValue = redisTemplate.opsForValue().get("lock");
//            if (uuid.equals(lockValue)) {
//                redisTemplate.delete("lock");
//            }
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then" +
                                "   return redis.call('del',KEYS[1]) " +
                                "else" +
                                "   return 0 " +
                                "end";
                Long unlockResp = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
            }

            return catelog2JsonFromDB;
        } else {
            return null;
        }
    }

    public Map<String, List<Catalog2Vo>> getCatelog2JsonFromDB() {
        log.info("未命中缓存，查询数据库");

        List<CategoryEntity> allCategorys = this.list();

        List<CategoryEntity> level1Categorys = getLevelCategorys(allCategorys, 0L);
        Map<String, List<Catalog2Vo>> catelog2Json = level1Categorys.stream().collect(Collectors.toMap(category -> {
            return String.valueOf(category.getCatId());
        }, category -> {
            List<CategoryEntity> level2Categorys = getLevelCategorys(allCategorys, category.getCatId());
            List<Catalog2Vo> catalog2Vos = null;
            if (level2Categorys != null) {
                catalog2Vos = level2Categorys.stream().map(item -> {
                    Catalog2Vo catalog2Vo = new Catalog2Vo();
                    catalog2Vo.setCatalog1Id(category.getCatId().toString());
                    catalog2Vo.setId(item.getCatId().toString());
                    catalog2Vo.setName(item.getName());

                    List<CategoryEntity> level3Categorys = getLevelCategorys(allCategorys, item.getCatId());
                    List<Catalog2Vo.Catalog3Vo> catalog3Vos = null;
                    if (level3Categorys != null) {
                        catalog3Vos = level3Categorys.stream().map(c3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo();
                            catalog3Vo.setCatalog2Id(c3.getParentCid().toString());
                            catalog3Vo.setName(c3.getName());
                            catalog3Vo.setId(c3.getCatId().toString());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                    }
                    catalog2Vo.setCatalog3List(catalog3Vos);
                    return catalog2Vo;
                }).collect(Collectors.toList());
            }

            return catalog2Vos;
        }));

        return catelog2Json;
    }

    private List<CategoryEntity> getLevelCategorys(List<CategoryEntity> allCategorys, Long parentId) {
        List<CategoryEntity> levelCategorys = allCategorys.stream()
                .filter(category -> category.getParentCid().equals(parentId))
                .collect(Collectors.toList());

        return levelCategorys;
    }

    public List<Long> findParentPath(Long catelogId, List<Long> path) {
        path.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if (entity.getParentCid() != 0) {
            findParentPath(entity.getParentCid(), path);
        }

        return path;
    }
}